// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Author: wsfuyibing <682805@qq.com>
// Date: 2024-11-19

package jaeger_plugin

import (
	"context"
	"encoding/base64"
	"encoding/binary"
	"fmt"
	"gitee.com/go-libs/log/common"
	"gitee.com/go-libs/log/conf"
	"gitee.com/go-libs/log/tracer/jaeger_plugin/jaeger"
	"gitee.com/go-libs/log/tracer/jaeger_plugin/thrift"
	"github.com/valyala/fasthttp"
	"net/http"
	"runtime"
	"strconv"
	"time"
)

// 上报工具.
// 上报链路跟踪数据到 Jaeger 服务端.
type reporter struct {
	timeout time.Duration
}

// +------------------------------------------------------------------------------+
// | Interface methods                                                            |
// +------------------------------------------------------------------------------+

func (o *reporter) Report(_ common.SpanFormatter, spans []common.Span) (err error) {
	var (
		batch = jaeger.NewBatch()
		buf   = thrift.NewTMemoryBuffer()
		cfg   = conf.New()
		data  []byte

		request  = fasthttp.AcquireRequest()
		response = fasthttp.AcquireResponse()
	)

	defer func() {
		fasthttp.ReleaseRequest(request)
		fasthttp.ReleaseResponse(response)
	}()

	// 1. 进程信息.
	batch.Process = jaeger.NewProcess()
	batch.Process.ServiceName = cfg.ServiceName
	batch.Process.Tags = make([]*jaeger.Tag, 0)

	// 1.1 进程属性.
	for pk, pv := range map[string]any{
		"Env":  fmt.Sprintf(`%s/%s %s`, runtime.GOOS, runtime.GOARCH, runtime.Version()),
		"Pid":  cfg.Pid,
		"Port": cfg.ServicePort,
	} {
		batch.Process.Tags = append(batch.Process.Tags, o.buildJaegerTag(pk, pv))
	}

	// 2. 跟踪信息.
	batch.Spans = make([]*jaeger.Span, 0)

	// 2.1 遍历跨度.
	for _, v := range spans {
		if x := o.buildJaegerSpan(v); x != nil {
			batch.Spans = append(batch.Spans, x)
		}
	}

	// 3. 格式转换.
	if batch.Write(context.Background(), thrift.NewTBinaryProtocolConf(buf, &thrift.TConfiguration{})) == nil {
		data = buf.Buffer.Bytes()
	}

	// 4. 发送请求.
	request.SetRequestURI(cfg.JaegerTracer.Endpoint)
	request.SetBody(data)
	request.Header.SetMethod(http.MethodPost)
	request.Header.SetContentType("application/x-thrift")

	// 4.1 鉴权信息.
	if usr := cfg.JaegerTracer.Username; usr != "" {
		pwd := cfg.JaegerTracer.Password
		request.Header.Set("Authorization", fmt.Sprintf("Basic %s", base64.StdEncoding.EncodeToString([]byte(usr+":"+pwd))))
	}

	// 5. 请求过程.
	if err = fasthttp.DoTimeout(request, response, o.timeout); err == nil {
		if code := response.StatusCode(); code != http.StatusOK && code != http.StatusAccepted {
			err = fmt.Errorf(`HTTP %d %s`, code, http.StatusText(code))
		}
	}
	return
}

// +------------------------------------------------------------------------------+
// | Access methods                                                               |
// +------------------------------------------------------------------------------+

func (o *reporter) buildJaegerLog(v common.Item) (log *jaeger.Log) {
	log = jaeger.NewLog()
	log.Timestamp = v.Time().UnixMicro()
	log.Fields = make([]*jaeger.Tag, 0)

	for vk, vv := range map[string]any{
		"level": v.Level().String(),
		"log":   v.Message(),
		"time":  v.Time().Format(time.RFC3339),
	} {
		if g := o.buildJaegerTag(vk, vv); g != nil {
			log.Fields = append(log.Fields, g)
		}
	}
	return
}

func (o *reporter) buildJaegerSpan(v common.Span) (js *jaeger.Span) {
	// 1. 跨度信息.
	js = &jaeger.Span{
		Duration:      v.GetDuration().Microseconds(),
		Flags:         1,
		OperationName: v.GetName(),
		StartTime:     v.GetStartTime().UnixMicro(),
	}

	// 2. 链路ID.
	ss := v.GetTracing().GetSpanId().Byte()
	ts := v.GetTracing().GetTraceId().Byte()
	js.TraceIdHigh = int64(binary.BigEndian.Uint64(ts[0:8]))
	js.TraceIdLow = int64(binary.BigEndian.Uint64(ts[8:16]))
	js.SpanId = int64(binary.BigEndian.Uint64(ss))

	// 3. 上级跨度.
	if x := v.GetTracing().GetParentSpanId(); x != nil {
		js.ParentSpanId = int64(binary.BigEndian.Uint64(x.Byte()))
	}

	// 4. 属性信息.
	if x := v.GetAttr(); x != nil {
		js.Tags = make([]*jaeger.Tag, 0)

		for xk, xv := range x {
			if g := o.buildJaegerTag(xk, xv); g != nil {
				js.Tags = append(js.Tags, g)
			}
		}
	}

	// 5. 日志信息.
	if logs := v.GetLogs(); len(logs) > 0 {
		js.Logs = make([]*jaeger.Log, 0)

		for _, x := range logs {
			if g := o.buildJaegerLog(x); g != nil {
				js.Logs = append(js.Logs, g)
			}
		}
	}

	return
}

func (o *reporter) buildJaegerTag(key string, value any) (tag *jaeger.Tag) {
	// 1. 创建标签.
	tag = jaeger.NewTag()
	tag.Key = key

	// 2. 校验类型.
	switch value.(type) {
	case bool:
		v, _ := value.(bool)
		tag.VType = jaeger.TagType_BOOL
		tag.VBool = &v
	case float32, float64:
		v, _ := strconv.ParseFloat(fmt.Sprintf("%v", value), 64)
		tag.VType = jaeger.TagType_DOUBLE
		tag.VDouble = &v
	case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
		v, _ := strconv.ParseInt(fmt.Sprintf("%v", value), 10, 64)
		tag.VType = jaeger.TagType_LONG
		tag.VLong = &v
	case string:
		v := value.(string)
		tag.VType = jaeger.TagType_STRING
		tag.VStr = &v
	default:
		v := fmt.Sprintf("%v", value)
		tag.VType = jaeger.TagType_STRING
		tag.VStr = &v
	}
	return
}

func (o *reporter) init() *reporter {
	if n := conf.New().JaegerTracer.Timeout; n > 0 {
		o.timeout = time.Duration(n) * time.Second
	} else {
		o.timeout = time.Second * 5
	}

	return o
}
